-module(rtps_reg).

%%% @doc This module implements a generic registry used by the domain,
%%% participant, reader, writer, reader locator, reader proxy and
%%% writer proxy controlling processes to register their pid with
%%% their id. It implements the same API as the OTP `global' library
%%% module. A domain, participant etc. uses the {via, ...} construct
%%% on starting the controlling process to register its
%%% id. Registering, re-registering and unregistering is now fully
%%% taken care of by OTP. The registry is queried to find the pid
%%% belonging to some id, for example a domain id.

-behaviour(gen_server).

-include("rtps.hrl").

%% API
-export([start_link/0, register_name/2, unregister_name/1,
	 whereis_name/1, whereis_name/2, send/2, which/1]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
	 terminate/2, code_change/3]).
			
-type id() :: rtps:domain_id() | % for domains
	      rtps:guid_prefix() | % for participants
	      rtps:entity_id() | % for writers and readers
	      rtsp:locator() | % for reader-locators
	      rtps:guid(). % for reader-proxies and writer-proxies

-record(name, {id :: id(), pid :: pid()}).
-record(data, {names = [] :: [#name{}]}).

%%%===================================================================
%%% API
%%%===================================================================

start_link() ->
    gen_server:start_link(?MODULE, [], []).

-spec register_name({Reg, Id}, Pid) -> yes | no when
      Reg :: pid(),
      Id :: id(),
      Pid :: pid().
%% @doc Associates `Id' with a `Pid' using registry `Reg'.
register_name({Reg, Id}, Pid) ->
    gen_server:call(Reg, {register, Id, Pid}).

-spec unregister_name({Reg, Id}) -> ok when
      Reg :: pid(),
      Id :: id().
%% @doc Removes the registered `Id' from the registry `Reg'.
unregister_name({Reg, Id}) ->
    gen_server:cast(Reg, {unregister, Id}).

-spec whereis_name(Reg, Id) -> Pid | undefined when
      Reg :: pid(),
      Id :: id(),
      Pid :: pid().
%% @doc Returns the pid of the registered `Id' in regsitry `Reg'. Returns
%% `undefined' if the `Id' is not registered.
whereis_name(Reg, Id) ->
    whereis_name({Reg, Id}).

-spec whereis_name({Reg, Id}) -> Pid | undefined when
      Reg :: pid(),
      Id :: id(),
      Pid :: pid().
%% @doc Returns the pid of the registered `Id' in regsitry `Reg'. Returns
%% `undefined' if the `Id' is not registered.
whereis_name({Reg, Id}) ->
    gen_server:call(Reg, {whereis, Id}).

-spec send({Reg, Id}, Msg) -> Pid when
      Reg :: pid(),
      Id :: id(),
      Msg :: term(),
      Pid :: pid().
%% @doc Sends message `Msg' to the pid registered as `Id', using registry
%% `Reg'.
send({Reg, Id}, Msg) ->
    case whereis_name({Reg, Id}) of
        Pid when is_pid(Pid) ->
            Pid ! Msg,
            Pid;
        undefined ->
            exit({badarg, {Id, Msg}})
    end.

-spec which(Reg) -> [{Id, Pid}] when
      Reg :: pid(),
      Id :: id(),
      Pid :: pid().
%% @doc Returns a list with the currently registered entities in
%% registry `Reg'.
which(Reg) ->
    gen_server:call(Reg, which).
    
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================

%% @private
init([]) ->
    process_flag(trap_exit, true),
    {ok, #data{names = []}}.

%% @private
handle_call({register, Id, Pid}, _From, #data{names = Names} = Data) ->
    case lists:keyfind(Id, #name.id, Names) of
	false ->
	    Name = #name{id = Id, pid = Pid},
	    link(Pid),
	    {reply, yes, Data#data{names = [Name | Names]}};
	_ ->
	    {reply, no, Data}
    end;
handle_call({whereis, Id}, _From, #data{names = Names} = Data) ->
    case lists:keyfind(Id, #name.id, Names) of
	#name{pid = Pid} when is_pid(Pid) ->
	    {reply, Pid, Data};
	false ->
	    {reply, undefined, Data}
    end;
handle_call(which, _From, #data{names = Names} = Data) ->
    Reply = [{Id, Pid} || #name{id = Id, pid = Pid} <- Names],
    {reply, Reply, Data};
handle_call(_Request, _From, Data) ->
    Reply = ok,
    {reply, Reply, Data}.

%% @private
handle_cast({unregister, Id}, #data{names = Names} = Data) ->
    case lists:keytake(Id, #name.id, Names) of
	{value, #name{pid = Pid}, Names_} ->
	    unlink(Pid),
	    {noreply, Data#data{names = Names_}};
	false ->
	    {noreply, Data}
    end;
handle_cast(_Msg, Data) ->
    {noreply, Data}.

%% @private
handle_info({'EXIT', From_pid, Reason}, #data{names = Names} = Data) ->
    case lists:keytake(From_pid, #name.pid, Names) of
	{value, _Name, Names_} ->
	    {noreply, Data#data{names = Names_}};
	false ->
	    {stop, Reason, Data}
    end;
handle_info(_Info, Data) ->
    {noreply, Data}.

%% @private
terminate(_Reason, _Data) ->
    ok.

%% @private
code_change(_OldVsn, Data, _Extra) ->
    {ok, Data}.

%%%===================================================================
%%% Internal functions
%%%===================================================================

